
#import "NekoView.h"
#import "Thinker.h"

// based on xneko by Masayuki Koba
// hacked into NeXTstep by Eric P. Scott
// severely munged into BackSpace by sam streeper
// go back to the X sources if you port this to other platforms...

#import <libc.h>
#import <math.h>
#import <defaults/defaults.h>
#import <appkit/graphics.h>
#import <appkit/Application.h>
#import <appkit/Window.h>
#import <appkit/NXImage.h>
#import <dpsclient/wraps.h>

const NXCoord Even[]={
	Mati2,	// STATE_STOP
	Jare2,	// STATE_JARE
	Kaki1,	// STATE_KAKI
	Mati3,	// STATE_AKUBI
	Sleep1,	// STATE_SLEEP
	Awake,	// STATE_AWAKE
	Up1,	// STATE_U_MOVE
	Down1,	// STATE_D_MOVE
	Left1,	// STATE_L_MOVE
	Right1,	// STATE_R_MOVE
	UpLeft1,	// STATE_UL_MOVE
	UpRight1,	// STATE_UR_MOVE
	DownLeft1,	// STATE_DL_MOVE
	DownRight1,	// STATE_DR_MOVE
	UpTogi1,	// STATE_U_TOGI
	DownTogi1,	// STATE_D_TOGI
	LeftTogi1,	// STATE_L_TOGI
	RightTogi1	// STATE_R_TOGI
};

const NXCoord Odd[]={
	Mati2,	// STATE_STOP
	Mati2,	// STATE_JARE
	Kaki2,	// STATE_KAKI
	Mati3,	// STATE_AKUBI
	Sleep2,	// STATE_SLEEP
	Awake,	// STATE_AWAKE
	Up2,	// STATE_U_MOVE
	Down2,	// STATE_D_MOVE
	Left2,	// STATE_L_MOVE
	Right2,	// STATE_R_MOVE
	UpLeft2,	// STATE_UL_MOVE
	UpRight2,	// STATE_UR_MOVE
	DownLeft2,	// STATE_DL_MOVE
	DownRight2,	// STATE_DR_MOVE
	UpTogi2,	// STATE_U_TOGI
	DownTogi2,	// STATE_D_TOGI
	LeftTogi2,	// STATE_L_TOGI
	RightTogi2	// STATE_R_TOGI
};

#define	PI_PER8			((double)M_PI/(double)8)
double	SinPiPer8Times3;
double	SinPiPer8;

@implementation NekoView

- TickCount
{
	if ( ++NekoTickCount >= MAX_TICK )
		NekoTickCount = 0;

	if ( NekoTickCount % 2 == 0 )
		if ( NekoStateCount < MAX_TICK )
			NekoStateCount++;

	return self;
}

- SetNekoState: (int) SetValue
{
	NekoTickCount=0;
	NekoStateCount=0;

	NekoState=SetValue;
	return self;
}

- DrawNeko: (NXCoord) DrawIcon
{ 
	NekoLastIcon.origin.x=DrawIcon;

	if (NX_X(&NekoPos) != NekoLastXY.x || NX_Y(&NekoPos)!=NekoLastXY.y)
	{
		static const NXRect zero={ { Space, 0.0 },
			{ (NXCoord)BITMAP_WIDTH, (NXCoord)BITMAP_HEIGHT } };

		[bitmaps composite:NX_SOVER fromRect:&zero toPoint:&NekoLastXY];		
	}

	[bitmaps composite:NX_SOVER fromRect:&NekoLastIcon
		toPoint:&NekoPos.origin];

	NekoLastXY=NekoPos.origin;
	return self;
}

- NekoDirection
{
	int			NewState;
	double		LargeX, LargeY;
	double		Length;
	double		SinTheta;

	if ( NekoMoveDx == 0 && NekoMoveDy == 0 )
		NewState = STATE_STOP;
	else
	{
	LargeX = (double)NekoMoveDx;
	LargeY = (double)(-NekoMoveDy);
	Length = sqrt( LargeX * LargeX + LargeY * LargeY );
	SinTheta = LargeY / Length;

	if ( NekoMoveDx > 0 )
	{
		if ( SinTheta > SinPiPer8Times3 )
			NewState = STATE_U_MOVE;
		else if ((SinTheta <= SinPiPer8Times3) && (SinTheta > SinPiPer8))
			NewState = STATE_UR_MOVE;
		else if ((SinTheta <= SinPiPer8) && (SinTheta > -(SinPiPer8)))
			NewState = STATE_R_MOVE;
		else if ((SinTheta <= -SinPiPer8) && (SinTheta > -SinPiPer8Times3))
			NewState = STATE_DR_MOVE;
		else
			NewState = STATE_D_MOVE;
	}
	else
	{
		if ( SinTheta > SinPiPer8Times3)
			NewState = STATE_U_MOVE;
		else if ((SinTheta <= SinPiPer8Times3) && (SinTheta > SinPiPer8))
			NewState = STATE_UL_MOVE;
		else if ((SinTheta <= SinPiPer8 ) && (SinTheta > -SinPiPer8))
			NewState = STATE_L_MOVE;
		else if ((SinTheta <= -SinPiPer8) && (SinTheta > -SinPiPer8Times3))
			NewState = STATE_DL_MOVE;
		else
			NewState = STATE_D_MOVE;
	}
	}

	if ( NekoState != NewState )
		[self SetNekoState: NewState];

	return self;
}

- (BOOL) IsNekoDontMove
{
	if ( NX_X(&NekoPos) == NekoLastXY.x && NX_Y(&NekoPos) == NekoLastXY.y )
		return( YES );

	return( NO );
}

- (BOOL) IsNekoMoveStart
{
	if ( ( PrevMouseX >= MouseX - IDLE_SPACE
			&& PrevMouseX <= MouseX + IDLE_SPACE ) &&
			( PrevMouseY >= MouseY - IDLE_SPACE 
			&& PrevMouseY <= MouseY + IDLE_SPACE ) )
		return( NO );

	return( YES );
}

- CalcDxDy
{
	double	LargeX, LargeY;
	double	DoubleLength, Length;

	if (randCount1 == -100)		// a signal to let values be...
		randCount1 = 200;
	else
	{
  		PrevMouseX = MouseX;
		PrevMouseY = MouseY;
	}

	if (--randCount1 < 0)
	{
		randCount1 = (random() % (300));
		MouseX = randBetween(-90,bounds.size.width+90);
		MouseY = randBetween(-90,bounds.size.height+90);
	}

	LargeX = (double)( MouseX - NX_X(&NekoPos) - BITMAP_WIDTH/2 );
	LargeY = (double)( MouseY - NX_Y(&NekoPos));

	DoubleLength = LargeX * LargeX + LargeY * LargeY;

	if ( DoubleLength != (double)0 )
	{
		Length = sqrt( DoubleLength );
		if ( Length <= NekoSpeed ) {
			NekoMoveDx = (int)LargeX;
			NekoMoveDy = (int)LargeY;
		} else {
			NekoMoveDx = (int)( ( NekoSpeed * LargeX ) / Length );
			NekoMoveDy = (int)( ( NekoSpeed * LargeY ) / Length );
		}
	}
	else NekoMoveDx = NekoMoveDy = 0;

	return self;
}


- oneStep
{
	usleep(1000000 / 8);	// run at about 8 frames/sec

	[self CalcDxDy];

	if ( NekoState != STATE_SLEEP )
		[self DrawNeko: NekoTickCount % 2 == 0 ?
			Even[NekoState] : Odd[NekoState]];
	else [self DrawNeko: NekoTickCount % 8 <= 3 ?
			Even[NekoState] :Odd[NekoState]];

	[self TickCount];

	switch ( NekoState ) 
	{
	case STATE_STOP:
		if ( [self IsNekoMoveStart])
		{
			[self SetNekoState: STATE_AWAKE ];
			break;
		}
		if ( NekoStateCount < TIME_STOP )
			break;

		if ( NekoMoveDx < 0 && NX_X(&NekoPos) <= 0 )
			[self SetNekoState: STATE_L_TOGI ];

		else if ( NekoMoveDx > 0 &&
				NX_X(&NekoPos) >= NX_WIDTH(&bounds) - BITMAP_WIDTH )
			[self SetNekoState: STATE_R_TOGI ];

		else if ( NekoMoveDy < 0 && NX_Y(&NekoPos) <= 0 )
			[self SetNekoState: STATE_U_TOGI ];

		else if ( NekoMoveDy > 0 && NX_Y(&NekoPos) >=
				NX_HEIGHT(&bounds) - BITMAP_HEIGHT )
			[self SetNekoState: STATE_D_TOGI ];

		else [self SetNekoState: STATE_JARE ];

		break;

	case STATE_JARE:
		if ( [self IsNekoMoveStart] )
		{
			[self SetNekoState: STATE_AWAKE ];
			break;
		}
		if ( NekoStateCount < TIME_JARE )
			break;

		[self SetNekoState: STATE_KAKI ];
		break;

	case STATE_KAKI:
		if ( [self IsNekoMoveStart])
		{
			[self SetNekoState: STATE_AWAKE ];
			break;
		}
		if ( NekoStateCount < TIME_KAKI )
			break;

		[self SetNekoState: STATE_AKUBI ];
		break;

	case STATE_AKUBI:
		if ( [self IsNekoMoveStart] )
		{
			[self SetNekoState: STATE_AWAKE ];
			break;
		}
		if ( NekoStateCount < TIME_AKUBI )
			break;

		[self SetNekoState: STATE_SLEEP ];
		break;

	case STATE_SLEEP:
		if ( [self IsNekoMoveStart] )
		{
			[self SetNekoState: STATE_AWAKE ];
			break;
		}
		break;

	case STATE_AWAKE:
		if ( NekoStateCount < TIME_AWAKE )
			break;

		[self NekoDirection];
		break;

	case STATE_U_MOVE:
	case STATE_D_MOVE:
	case STATE_L_MOVE:
	case STATE_R_MOVE:
	case STATE_UL_MOVE:
	case STATE_UR_MOVE:
	case STATE_DL_MOVE:
	case STATE_DR_MOVE:
		NekoPos.origin.x += NekoMoveDx;
		NekoPos.origin.y += NekoMoveDy;
		[self NekoDirection];
		[self NekoAdjust];

		if ( [self IsNekoDontMove])
			[self SetNekoState: STATE_STOP ];

		break;

	case STATE_U_TOGI:
	case STATE_D_TOGI:
	case STATE_L_TOGI:
	case STATE_R_TOGI:
		if ( [self IsNekoMoveStart] )
		{
			[self SetNekoState: STATE_AWAKE ];
			break;
		}
		if ( NekoStateCount < TIME_TOGI )
			break;

		[self SetNekoState: STATE_KAKI ];
		break;

	default:
		/* Internal Error */
		[self SetNekoState: STATE_STOP ];
		break;
	}

	return self;
}

- NekoAdjust
{
	if ( NX_X(&NekoPos) < 0 )
		NekoPos.origin.x = 0;
	else if ( NX_X(&NekoPos) > NX_WIDTH(&bounds) - BITMAP_WIDTH )
		NekoPos.origin.x = NX_WIDTH(&bounds) - BITMAP_WIDTH;

	if ( NX_Y(&NekoPos) < 0 )
		NekoPos.origin.y = 0;
	else if ( NX_Y(&NekoPos) > NX_HEIGHT(&bounds) - BITMAP_HEIGHT )
		NekoPos.origin.y = NX_HEIGHT(&bounds) - BITMAP_HEIGHT;

	return self;
}

- setBitmaps:anObject
{
	bitmaps = anObject;
	return self;
}

- bitmaps
{
	return bitmaps;
}

- initFrame:(const NXRect *)frameRect
{
	SinPiPer8Times3=sin(PI_PER8*(double)3);
	SinPiPer8=sin(PI_PER8);

	[super initFrame:frameRect];
	[self setClipping:NO];
	[self allocateGState];

	NXSetRect(&NekoPos,
		(NX_WIDTH(frameRect)-(NXCoord)(BITMAP_WIDTH/2))/2.0,
		(NX_HEIGHT(frameRect)-(NXCoord)(BITMAP_HEIGHT/2))/2.0,
		(NXCoord)BITMAP_WIDTH, (NXCoord)BITMAP_HEIGHT);
	NekoLastXY=NekoPos.origin;

	bitmaps=[self findImageNamed:"browncat"];

	NXSetRect(&NekoLastIcon, Even[STATE_STOP], 0.0,
		(NXCoord)BITMAP_WIDTH, (NXCoord)BITMAP_HEIGHT);
	NekoSpeed=14;
	[self SetNekoState: STATE_STOP];
	randCount1 = 100;
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	if (!rects || !rectCount) return self;
	PSsetgray(0);
	NXRectFill(rects);

	[bitmaps composite:NX_SOVER fromRect:&NekoLastIcon
		toPoint:&NekoPos.origin];

	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width:height];
	[self NekoAdjust];
	return self;
}

- (const char *) windowTitle
{	return "Neko";
}

- inspector:sender
{
	char buf[MAXPATHLEN];
	
	if (!inspectorPanel)
	{
		sprintf(buf,"%s/Neko.nib",[sender moduleDirectory:"Neko"]);
		[NXApp loadNibFile:buf owner:self withNames:NO];
	}
	return inspectorPanel;
}

- (BOOL) useBufferedWindow
{	return YES;
}

- findImageNamed:(const char *)name
{
	char buf[1024];
	id ret = [NXImage findImageNamed:name];
	if (ret) return ret;

	sprintf(buf,"%s/%s.tiff",[(BSThinker()) moduleDirectory:"Neko"],name);
	ret = [[NXImage alloc] initFromFile:buf];
	[ret setName:name];
	return ret;
}

- inspectorWillBeRemoved
{
	[infoWindow orderOut:self];
	return self;
}

- mouseDown:(NXEvent *)event
{
	NXPoint loc = event->location;
	[self convertPoint:&loc fromView:nil];
	randCount1 = -100;
  	PrevMouseX = MouseX;
	PrevMouseY = MouseY;
	MouseX = loc.x;
	MouseY = loc.y;
	return self;
}


@end
